分享 | 基于kubernetes的VM解决方案探讨
供稿 | eBay Infrastructure Engineering 刘应科
翻译&编辑 | 顾欣怡本文3526字,预计阅读时间11分钟更多干货请关注“eBay技术荟”公众号导读
随着kubernetes的发展壮大,越来越多的公司开始基于kubernetes构建云平台,eBay也不例外。为了便于同时管理kubernetes和OpenStack集群,eBay选用了virtlet管理方案,并在其基础上做了相应的定制和改进,以增强其可靠性和性能,从而更好地管理eBay云平台。
一、背景
eBay从2015年就开始适配kubernetes平台并逐渐部署各个团队的产品。然而eBay仍然部署着很大规模的OpenStack集群。同时管理kubernetes集群和OpenStack集群需要耗费更多的人力和物力。但由于eBay内部还有一部分业务无法迁移到容器,我们能否用一套控制平面(control plane)同时管理容器和虚拟机呢?
二、已有的方案
在几年的时间里,kubernetes不断发展壮大,各个功能逐渐完善,越来越多的公司基于kubernetes构建云平台。在eBay内部,越来越多的产品基于kubernetes构建,因此用kubernetes来统一云平台是大势所趋,我们需要一种方案来基于kubernetes管理虚拟机。
现在社区有两套相对成熟的基于kubernetes来管理虚拟机的方案,分别是kubevirt和virtlet。
1. Kubevirt
Kubevirt是redhat发起的项目,它使用CRD去描述一个VM(Virtual Machine,虚拟机),通过它的控制器(controller)去把CRD转换成一个POD。由于它使用的是CRD而不是POD,导致需要额外的控制器来实现kubernetes里原生的部署和服务(deployment,service)这些功能。VM的实例运行在容器(container)里,一个VM有对应的libvirt来管理。Kubevirt的社区比较活跃,但版本还在较早的阶段。
2. Virtlet
Virtlet来自于Mirantis,跟kubevirt的不同之处在于它使用POD来描述一个VM。因为kubelet是通过CRI接口跟下面的运行时交互的,virtlet实现了一套VM的CRI。因为POD是kubernetes的一等公民,任何现有的kubernetes功能都可以用于virtlet管理的VM,且不需要额外的控制器,比如服务、部署等等,这样几乎不需要额外的学习和维护成本。但因为一些VM特定的信息无法完全用POD来描述,virtlet借助了POD的注解(annotation)来表达更多VM的信息。
三、我们的选择
1. Virtlet概览
图1是来自virtlet社区的架构图。一个节点上virtlet POD包含三个容器:
virtlet:接收CRI调用,管理VM
libvirt:接收virtlet的请求创建、停止或销毁VM
VMs:所有virtlet管理的VM都会在这个容器的命名空间里
图1(点击可查看大图)
2. 运行时
图2(点击可查看大图)
镜像:CRIProxy通过区分一个可配置的前缀来区分是一个容器镜像还是一个VM镜像。 运行时:CRIProxy通过特定的注解来区分容器或VM。
因为引入了CRIProxy,多了一层中间调用,使这个架构看起来不够统一,多了一层可能的出错点,增加了出问题的几率以及调试的难度。所以我们下阶段计划开发一个VM shim,如图3所示。它满足containerd的shim规范。这样单个节点的运行时看起来更统一和干净。
图3(点击可查看大图)
四、模型
刚有提到,操作VM的入口是在AZ层,而且已经有了CRD来描述一个节点。同一个AZ管理着一个或者多个kubernetes集群。
图4(点击可查看大图)
这里的provider是virtlet,专门管理VM的生命周期。如图4所示,它主要的任务是根据ComputeNode,首先挑选一个kubernetes集群,然后在这个集群里创建一个对应VM的POD,同步他们之间的状态。对于用户而言,下面的POD是不可见的,用户通过创建或者删除ComputeNode来管理VM。
五、定制
Virtlet已经有了大部分所需要的功能,但还不能完全满足需求,我们对以下方面做了定制和改进:
VM网络
NUMA Pin VM的重启、停止 OpenStack镜像兼容 Virtlet的可靠性
1. VM网络
对于现有的eBay VM,都是桥接(bridge)网络,只需要在libvirt domain指定目标桥接器,libvirt就会在启动VM的时候创建一个TAP接口,并把这个接口连接到指定的桥接器。
然而对于kubernetes的VM,网络接口是调用CNI插件先配置好,然后才会创建和启动VM。Virtlet引入了vmwrapper,它是所有VM启动的入口,virtlet会把vmwapper设置成libvirt domain的入口(emulator)。
图5(点击可查看大图)
从图5可见,启动一个libvirt实例后,vmwrapper会被首先执行:
Virtlet侦听在一个unix domain socket上,并且virtlet已经打开了相应VM的TAP接口。
vmwrapper发起连接跟virtlet通信,拿到TAP接口的文件描述符(FD)。
vmwrapper填写好网络相关的参数,最后启动真正的qemu进程。
qemu网络参数举例如下:
这里涉及到了进程间传递描述符(FD),virtlet使用的是Linux的SCM(Socket level control messages)方式。
每一次CNI插件配置网络,都会建一个新的网络命名空间(network namespace),一般需要有一对veth pair来连接主机的网络和新建的网络空间。Virtlet默认VM网络如图6:
图6(点击可查看大图)
连通一个VM,需要建一个bridge,一对veth pair,这样网络的性能会有一定的影响。
VM本身有强隔离性,我们又有自己的CNI插件实现,为了减少网络的路径,创建VM的网络不需要新的网络命名空间。最终一个节点的网络结构如图7所示:
图7(点击可查看大图)
Virtlet不支持vhost net,而eBay OpenStack上使用virtio的VM都有vhost net。增加这个功能,对每一个TAP接口,都需要有一个vhost net的描述符,这个描述符是通过打开/dev/vhost-net而来的。描述符的传递跟前面提到过的TAP描述符传递类似。
在对virtlet的VM进行网络性能测试的时候,网络吞吐量只有OpenStack VM的一半,在VM里面,soft IRQ负载很高,最终发现virtlet VM的网络没有打开TSO和TX/RX校验。
无论是Open Stack的VM还是virtlet的VM,都没有显示配置这个选项,但为什么OpenStack的VM默认是打开的呢?
前面提到,常规VM的网络接口是libvirt创建和删除的,而virtlet的VM网络接口是由CNI创建、virtlet管理的。通过阅读libvirt和qemu的源代码,libvirt在打开TAP接口的时候会加一个IFF_VNET_HDR的选项,qemu检查到这个选项后, VM实例的网络接口就会默认打开TSO。通过这个小小的改动,virtlet VM的网络吞吐量跟OpenStack的VM旗鼓相当。
2. NUMA Pin
eBay数据中心的服务器都有2个或以上的NUMA节点,在这些节点上运行的VM,需要把他们尽量固定在NUMA节点上,否则跨NUMA节点的访问会带来性能问题。Kubernetes有CPU manager这个功能,CPU的分配也顾及到了机器上的NUMA节点,但不能完全满足要求,原因如下:
CPU是独占的。也就是一个CPU被分配给一个POD后,其它任何POD就不能再分配到这个CPU了,这样不能做到超售。
CPU的分配可能会跨NUMA节点,但并不平衡。比如一个POD需要8个CPU,那就有可能2个CPU分配在一个NUMA节点,另外的6个CPU分配在另一个NUMA节点,导致了不平衡。
基于以上原因,我们没有使用CPU manager这个功能,而是在virtlet里增加了一个模块来管理NUMA节点的分配和释放(这也是virtlet有自己的CRI实现的好处之一):
固定VM所运行的NUMA节点,但不固定VM运行在NUMA节点对应的CPU。比如一台机器有2个NUMA节点,CPU 0-15在NUMA 0,CPU 16-31在NUMA 1,如果申请一个4核的VM,那这个VM只会运行在其中的一个NUMA节点上,但VM能用到的CPU可以是这个NUMA节点上所有的CPU(当然这只是标准的VM,对于有高性能要求的VM,既要有NUMA固定也需要有CPU固定)。
对于NUMA的分配,采取的是CPU、内存最平衡的方式,也就是在一个NUMA节点上,在分配量不超过某个阈值的前提下,计算已经分配的CPU和内存,加上将要被分配的VM需要的CPU和内存,CPU分配量和内存分配量差值的绝对值哪个最小,哪个优先级就最高。
3. VM的重启和停止
综合当前VM的实际状态和用户所期望的状态,做到最后状态一致。 增加一个POD的注解,里面包含所需要(request)的状态和实际(status)状态。
在给kubelet汇报VM状态的时候,如果用户显示的是该VM已停止,仍然要向kubelet汇报是运行状态,这样kubelet就不会反复地调用启动的接口。
4. OpenStack镜像兼容
eBay运行着很大规模的OpenStack集群,有许多已有的VM镜像,virtlet必须能无缝地使用这些已有的镜像。
在eBay,绝大部分OpenStack VM的网络信息是通过configdrive静态注入的方式,由cloutinit来完成网络的配置的,虽然virtlet的文档里声称支持configdrive,但他们使用的是不同的configdirve的版本,大部分的现有镜像都不能正确拿到configdrive所注入的信息。
因此需要实现另一个版本的configdrive,甚至还能兼容Windows VM。满足这两个需求,有两个关键的地方:
Configdrive盘必须有config-2的标签。
Configdrive必须是vfat格式的(虽然configdrive可以是vfat或者iso,但某些Windows的cloudinit不能识别ISO格式)。
六、可靠性
尽管VM的生命周期用kubernetes来管理,但VM和容器还是有不同的地方:在VM的生命周期之内,不管是POD重启、virtlet重启以及节点重启,VM的所有数据不能有任何丢失;而容器在重启之后数据就会消失(不包括host path)。
Virtlet要能用在生产环境,必须做到数据不丢失。还有节点上的VM能够脱离virtlet运行良好,这样才能给virtlet升级。从一开始,我们就非常关注virtlet的可靠性,也找到了virtlet可靠性所存在的一些问题:
升级virtlet本身,所有的VM也莫名其妙地消失了。
重启节点,VM所有的数据丢失。
停止VM和virtlet,然后再启动virtlet和VM,VM再也不能启动。
如果以上的问题不能解决,virtlet就不能用在生产环境,需要用其它方案甚至重新开发一套来代替。幸运的是通过积极阅读代码和调试,我们解决了上面的问题,社区也接受了这些补丁。
对于基于virtlet的一些改进和定制,需要保证每次改动不影响已有的功能,从一开始我们就开发了集成测试和端到端测试来保证可靠性。
七、性能
有了可靠的保障,性能也必须达标,参照物就是已有的OpenStack VM。
我们使用各种基准测试工具对相同CPU、内存以及磁盘的virtlet VM和OpenStack VM进行测试。
使用生产环境的镜像流量来比较两者的CPU和内存使用率,以及在每秒时间内能够处理的事务(TPS Transaction Per Second)。
比较下来的结果是两者没有差距,达到了我们的预期和要求。
八、结语
Kubernetes愈来愈受各家公司重视,这是未来几年甚至数十年云平台的趋势。但是从老的平台过渡到新的平台需要时间,一刀切的方式会带来无法遇见的风险和额外的耗时,特别是像eBay这样有多样产品和大体量的云平台公司。用kubernetes来管理VM使这种过渡成为了可能,既不需要同时维护两套平台,也减少了新老交替带来的风险。
↓点击阅读原文,一键投递
eBay大量优质职位,等的就是你!